字符匹配 真正理解KMP算法的力量 ( 修正)

问题提出:
给定两字符串 s[], t[]. 实现函数 find( const char s[], const char t[], int pos ),功能为从字符串s[] 的第pos个位置起(按照C++中数组下标规定,以0开始计数)开始查找t[]在s[]中第一次出现的位置,若干s[]中不存在t[],返回-1.
// 解法源代码下载地址:KMP.cpp & KMP.exe

分析:这道题很自然的想法就是 查找-回溯,代码见 find_1 函数。此算法最易想到,可是时间复杂度为 O(m*n),其中m,n 分别为s[],t[]的大小。

此类字符匹配有一个经典算法,KMP算法(由Knuth、Morris、Pratt共同提出)。这个算法非常好的“预处理”了要用于查找的t[].即发现t[]串的特点,这个特点能够使查找时指示s[]的指针i不回溯,只移动用于指示t[]的指针j。


next[] 算法:
1、next[0]=-1, next[1]=0;令k=next[j],
2、 如果  t[k]=t[j], 则 next[j+1]=k+1;
3、否则令k=next[k],比较 t[ k] ]=t[j],等则next[j+1]=k+1,不等则继续此步操作。
4、若一直不等,则 next[j+1]=0;



nextval1[] 算法:( 在已经求得next[]的基础上)
1、令k=next[j], 如果  t[k]==t[j], 令k=next[k],比较t[k] 与 t[j];若等,继续此操作直到不等的情况。
2、若不能,则next[j]=k;

当然,你应该已经看出其实next[] 和 nextval1[]的运算可以合并为一个函数完成,那就是get_nextval[] 函数。


在纸上画出 s[], t[]就可以体会为什么能够这样运算,我的理解是尽量使j值更小,也就是不断地切分t[]从0开始的一串(和从t[j-1]倒数相同的一串)。使这串的值最小。

以下是实现的全部代码》
find_1 普通实现方法。用count1记录“基本运算”(比较)次数,代码中的次数为56。
find_KMP :KMP算法,当使用未优化的KMP使 count2 记录为 36 , 当用优化的KMP算法时 count2记录为24. 
// 因为数组从 0 开始计数,所以代码中一些地方使用了-1作为标志。
#include <iostream>
using namespace std;

int count1 =0, count2=0;

int find_1( const char s[], const char t[], int pos )
{
 int i = pos, j =0;
 while( s[i]!='/0' && t[j]!='/0' )
 {
  if( s[i] == t[j] ) { ++i; ++j; }
  else { i = i-( j-1 ); j=0; } // 这里的i 和后面放回时i 的值最好用特殊法定,既方便又不易出错。
  ++count1;
 }
 if( t[j]=='/0' ) return i-j;  // 这句不能放在while循环里,否则无法判断找不到匹配字符串的情况。
 else return -1;  // -1 表示没有匹配字符
}

void get_next( const char t[ ], int next[ ] )  // 未优化的next[ ]
{
 int j = 0, k = *(next+0) = -1;    // 不能直接定义出next[1]=0, 因为有可能要查找的串只有一个字符。
 while( t[j+1] != '/0' )
 {
  if( k==-1 || t[j]==t[k] )
  {
   ++j; ++k; *(next+j) = k; // j>=1 时,等价于“若满足则next[j+1]=next[j]+1; ”.
  }
  else k=*(next+k);  // 不满足时,j 不变(因为没有进入上面的if语句),k 赋值为next[k], 继续进入if 比较t[j]==t[k].等价于分析时的不等则t[j] == t[ next[k] ];
 }
}

void get_nextval( const char t[ ], int next[ ] )  // 一步实现优化
{
 int j = 0,  k = -1; *(next+0)=-1;
 while( t[j+1]!='/0' )
 {
  if( k==-1 || t[j]==t[k] )
  {
   ++j; ++k;
   if( t[j] !=t[k] ) *(next+j)=k;
   else *(next+j) = *(next+k);
  }
  else k=*(next+k);
 }
}

void get_nextval1( const char t[ ], int next[ ] )  // 在已知next[]基础上实现优化
{
 int j =0, k;
 while( t[j+1] != '/0' )
 {
  if( j==0 ) { ++j; continue; }  // 若是第一个位置,则掠过。
  k = *(next+j);
  while( t[j]==t[k] ) { k=*(next+k);  } 
  *(next+j) = k;
  ++j;  
 }
}


int find_KMP( const char s[], const char t[],  int pos )
{
 int size = sizeof(t)/sizeof(t[0]) -1;
 int * next = new int[size];

 get_nextval( t, next );  // 一步实现优化next[]
// get_next( t, next ); get_nextval1( t, next ); // 两部实现优化next[]
// get_next( t, next );    // 未优化的next[]

 int i = pos, j =0;
 while( s[i]!='/0' && t[j]!='/0' )
 {
  if( j==-1 || s[i] == t[j] ) { ++i; ++j;  }
  else  j = *(next+j); 
  ++count2;
 }
 if( t[j]=='/0' ) return i-j;  
 else return -1;
}


void print( int a[], int n )
{
 for ( int i =0; i<n; ++i )
  cout<<a[i]<<'/t'<<flush;
 cout<<endl;
}

void print1( char a[], int n )
{
 for ( int i =0; i<n; ++i )
  cout<<a[i]<<"  "<<flush;
 cout<<endl;
}

void main()
{
 char s[]="aaaabaaaabaaaabaaaaab";
 char a[7] = "aaaaab";
 int n[6];
 
cout<<"待查找串为:"<<endl; print1( s, 22 );
cout<<"模式串为"<<endl;   print1( a,7);
 get_next( a, n );
 cout<<"未优化时的next[]值"<<endl;
 print( n, 6 );

 get_nextval1( a, n );
 cout<<"优化时的next[]值"<<endl;
 print( n, 6 );

 cout<<"t[]在s[]中出现的位置为:"<<find_1( s, a, 0 )<<endl;
 cout<<"普通方法使用了:"<<count1<<"步"<<endl;

 cout<<endl;
 cout<<"t[]在s[]中出现的位置为:"<<find_KMP( s, a, 0 )<<endl;
 cout<<"KMP优化方法使用了:"<<count2<<"步"<<endl;

 cin.get();
}


// KMP.exe has encountered a problem and needs to close.  We are sorry for the inconvenience.
// 可能错误原因:数组越界:因为我用-1 做了一些标记。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值